﻿namespace EFS.RC.CC.Tools.Hunters
{
	/// <summary>
	/// Operator dial in session
	/// </summary>
	internal class Group : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		// sync access
		private object syncAccess = new object();

		// Hunt conversation
		private Microsoft.Rtc.Collaboration.Conversation conversation = null;

		// Hunt call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall huntGroupAudioVideoCall = null;

		// customer session
		private EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession = null;

		/// group name
		private System.String groupName = null;

		/// group sip
		private System.String groupSip = null;

		/// user requesting
		private EFS.AD.User invitingUser = null;

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Hunters.Group> AsinkEstablishedDelegate { get; private set; }

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Hunters.Group> AsinkTerminatedDelegate { get; private set; }

		/// <summary>
		/// Status
		/// </summary>
		public enum SessionStatus { Idle, Started, Terminated }

		// Status
		protected SessionStatus status = SessionStatus.Idle;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public Group(EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession, EFS.AD.User invitingUser, System.String groupName, System.String groupSip)
		{
			// check
			EFS.Common.Verify.Reference(customerSession, "customerSession");
			EFS.Common.Verify.Reference(invitingUser, "invitingUser");
			EFS.Common.Verify.String(groupSip, "groupSip");
			EFS.Common.Verify.String(groupSip, "groupSip");

			// delegate
			AsinkTerminatedDelegate = new EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Hunters.Group>(this);
			AsinkEstablishedDelegate = new EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Hunters.Group>(this);

			// set
			this.customerSession = customerSession;
			this.invitingUser = invitingUser;
			this.groupName = groupName;
			this.groupSip = groupSip;

			// log
			EFS.EventLog.Log.LogInfo("DialIn", this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format(System.String.Format("EFS.RC.CC.Tools.Hunters.Group {0}", groupSip));
		}

		/// Group Sip	
		public System.String Sip { get { return groupSip; } }

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Begin shutdown
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal void HandleReplacedCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall)
		{
			// check
			EFS.Common.Verify.Reference(transferredAudioVideoCall, "transferredAudioVideoCall");

			// check not already handled
			// ~ commented for performance ~ EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Group Call is not Started");
				}

				// get app
				EFS.RC.CC.Applications.EndPoints.OfficeService.Operators operatorsOfficeServiceEndPointApplication = customerSession.
					CustomersOfficeServiceEndPointApplication.
					ApplicationPresence.
					EndPointsPresencesRegistrar.
					OperatorOfficeServiceEndPointPresence.
					PresenceEndPointApplication;

				// get agent for an external invite
				EFS.RC.CC.Presences.Operators.Presence presence = operatorsOfficeServiceEndPointApplication.OperatorsTeam.GetAgentBySIP(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);

				// check
				if (null == presence)
				{
					// make anon operator
					presence = new EFS.RC.CC.Presences.Operators.Anonymous(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);
				}

				// make a session
				EFS.RC.CC.Sessions.OfficeService.Operators.Transfer operatorTransferSession = new EFS.RC.CC.Sessions.OfficeService.Operators.Transfer(presence, customerSession, transferredAudioVideoCall);
				operatorTransferSession.AsinkEstablishedDelegate.Register(OnOperatorSessionEstablished);

				// start agent session
				operatorTransferSession.BeginStartup();
			}
		}

		// Status
		public SessionStatus Status
		{
			get { return status; }
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		protected void Startup()
		{
			// log
			EFS.EventLog.Log.LogInfo("Startup", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != SessionStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Group can be started");
				}

				// started
				status = SessionStatus.Started;
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "Startup", this);

			// register for terminate
			customerSession.OfficeServiceConference.AsinkTerminatedDelegate.Register(OnOfficeServiceConferenceTerminated);

			// establish call
			BeginAsyncProcess2(EstablishGroupAudioVideCall);
		}

		/// <summary>
		/// Call group
		/// </summary>
		private void EstablishGroupAudioVideCall()
		{
			// log
			EFS.EventLog.Log.LogInfo("EstablishGroupAudioVideCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// make an uri to impersonate into the back conversation
				System.String inpersonateUri = invitingUser.SIP;
				System.String inpersonateDisplayName = System.String.Format("{0} {1}", invitingUser.FirstName, invitingUser.LastName);

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate conversation {0}", inpersonateUri), this);

				// make office conversation
				conversation = new Microsoft.Rtc.Collaboration.Conversation(customerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint);
				conversation.Impersonate(inpersonateUri, null, inpersonateDisplayName);
				conversation.ApplicationContext = this;

				// call options
				huntGroupAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(conversation);

				// options
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions audioVideoCallEstablishOptions = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions();

				// establish
				huntGroupAudioVideoCall.BeginEstablish(groupSip, audioVideoCallEstablishOptions, EndEstablish, null);
			}
		}

		/// <summary>
		/// End dial out
		/// </summary>
		/// <param name="result"></param>
		private void EndEstablish(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndDialOut", this);

			try
			{
				// check
				huntGroupAudioVideoCall.EndEstablish(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "OnException", ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Office service conference terminated
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnOfficeServiceConferenceTerminated(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// check mine
			EFS.Common.Verify.Condition(customerSession.OfficeServiceConference.Equals(officeServiceConference), "Terminated Office Service Conference is different");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Agent session established
		/// </summary>
		private void OnOperatorSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());

			// notify established
			AsinkEstablishedDelegate.Start();

			// shut down
			BeginShutdown();
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			// log
			EFS.EventLog.Log.LogInfo("Shutdown", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (status != SessionStatus.Terminated)
				{
					// set terinated
					status = SessionStatus.Terminated;

					// terminate call
					AsinkTerminatedDelegate.Start();
				}
				else
				{
					// already terminated
					return;
				}
			}

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "Shutdown", this);

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// terminate all
			termination.Terminate(conversation);
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnShutdownComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
